/*
   BCRA CBU. Verification of key.

   Copyright 2010  A. Fernandez

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

package ar.gov.bcra;

import java.nio.CharBuffer;

import static java.lang.Character.*;
import static java.nio.CharBuffer.allocate;

/**
 * Immutable representation of a CBU (instances of this class are thread-safe.)
 * <p/>
 * Clave Bancaria Uniforme (CBU) is defined by
 * Banco Central de la Rep&uacute;blica Argentina (BCRA) in
 * Comunicaci&oacute;n "A" 2622.
 * <p/>
 *
 * @author A. Fernandez
 * @version 0.1, 10/10/2010
 */
public class CBU {

  /**
   * Creates a new key based on the input, calculates its verifier digits and verifies.
   *
   * @param input containing at least 22 digits (extra digits, and
   *              non-digits are ignored.)
   * @throws IllegalArgumentException if less than 22 digits are found.
   */
  public CBU(CharSequence input) {
    for (int i = 0; i < input.length() && pos < code.length; i++) {
      final char ch = input.charAt(i);
      if (isDigit(ch)) {
        code[pos++] = digit(ch, I10);
      }
    }

    if (pos != code.length) {
      throw new IllegalArgumentException(
        ARGUMENT_REQUIRES_22_DIGITS);
    }

    verifier1 = v1(code, 0, 6);
    verifier2 = v1(code, 8, 20);
    verified = verify();
  }

  private boolean verify() {
    return verifier1 == code[7] && verifier2 == code[21];
  }

  /**
   * Self explanatory.
   */
  public boolean isVerified() {
    return verified;
  }

  /**
   * Creates a string representing the code. (unofficial)
   */
  public String toString() {
    final CharBuffer buf = allocate(27);
    appendEntity(buf);
    appendDash(buf);
    appendBranch(buf);
    appendDash(buf);
    buf.append(forDigit(verifier1, I10));
    appendDash(buf);
    appendType(buf);
    appendDash(buf);
    appendAccount(buf);
    appendDash(buf);
    buf.append(forDigit(verifier2, I10));
    return buf.flip().toString();
  }

  /**
   * Creates a string representing the account.
   */
  public String account() {
    final CharBuffer buf = allocate(11);
    appendAccount(buf);
    return buf.flip().toString();
  }

  /**
   * Creates a string representing the entity.
   */
  public String entity() {
    final CharBuffer buf = allocate(3);
    appendEntity(buf);
    return buf.flip().toString();
  }

  /**
   * Creates a string representing the branch.
   */
  public String branch() {
    final CharBuffer buf = allocate(4);
    appendBranch(buf);
    return buf.flip().toString();
  }

  /**
   * Creates a string representing the type.
   */
  public String type() {
    final CharBuffer buf = allocate(2);
    appendType(buf);
    return buf.flip().toString();
  }

  @SuppressWarnings("unused")
  private static final int v1Alt(final int[] code, int pos1, int pos2) {
    int sum = 0;
    int len = 1 + pos2 - pos1;
    for (int i = 0; i < len; i++) {
      sum += code[pos2 - i] * M[3 - i % 4];
    }
    return v2(sum);
  }

  private static final int v1(final int[] code, int pos1, int pos2) {
    int sum = 0;
    for (int i = pos2, j = -1; i >= pos1; i--, j--) {
      if (j == -1) {
        j = 3;
      }
      sum += code[i] * M[j];
    }
    return v2(sum);
  }

  private static final int v2(int v1) {
    int mod = v1 % 10;
    return mod == 0 ? 0 : (10 - mod);
  }

  private static final void appendDash(final CharBuffer buf) {
    buf.append(DASH);
  }

  private void appendEntity(final CharBuffer buf) {
    append(buf, 0, 3);
  }

  private void appendBranch(final CharBuffer buf) {
    append(buf, 3, 4);
  }

  private void appendType(final CharBuffer buf) {
    append(buf, 8, 2);
  }

  private void appendAccount(final CharBuffer buf) {
    append(buf, 10, 11);
  }

  private void append(final CharBuffer buf, int i, final int len) {
    for (int j = 0; j < len; j++) {
      buf.append(forDigit(code[i++], I10));
    }
  }

  public static void main(String[] args) {
    System.out.println(new CBU("011 0138 2 02 01110037766 4").isVerified());
  }

  private static final String ARGUMENT_REQUIRES_22_DIGITS = "Argument requires 22 digits.";
  private static final char DASH = '-';
  private static final int I10 = 10;
  private static final int[] M = {9, 7, 1, 3}; // multiplier

  private final int[] code = new int[22];
  private int pos; // code slot
  private final int verifier1;
  private final int verifier2;
  private final boolean verified;

}
